#include "color.h"
#include "display.h"
#include "ge.h"
#include "gpu.h"
#include "vector.h"

void light(float colorOut[4], float uvInOut[2], float colorIn[4], Vec3 pos, Vec3 normal)
{
    Color4 emissive;
    Color4 globalAmbient;
    Vec3 norm = vec_normalized(&normal);
    Color4 ambient;
    Color4 diffuse;
    Color4 specular;
    Color4 in;
    float specCoef;
    Vec3 viewer;
    s32 tmapMode;
    s32 projStyle;
    s32 uLight, vLight;
    Color4 lightSum;
    s32 l;

    color_getFromRGB(&emissive, gstate.materialemissive);
    color_getFromRGB(&globalAmbient, gstate.ambientcolor);
    color_getFromA(&globalAmbient, gstate.ambientalpha);
    color_initFromArr(&in, colorIn);

    if (gstate.materialupdate & 1)
        ambient = in;
    else
        color_initFromArr(&ambient, gstate.materialambient);

    if (gstate.materialupdate & 2)
        diffuse = in;
    else {
        color_getFromRGB(&diffuse, gstate.materialdiffuse);
        diffuse.a = 1.0f;
    }

    if (gstate.materialupdate & 4)
        specular = in;
    else {
        color_getFromRGB(&specular, gstate.materialspecular);
        specular.a = 1.0f;
    }

    specCoef = getFloat24(gstate.materialspecularcoef);

    vec_normalize(&norm);

    vec_initfromxyz(&viewer, viewMatrix[8], viewMatrix[9], viewMatrix[10]);

    tmapMode = gstate.texMap & 3;
    projStyle = (gstate.texMap >> 8) & 3;

    uLight = gstate.texshade & 3;
    vLight = (gstate.texshade >> 8) & 3;

    lightSum = color_add(color_multiply(globalAmbient, ambient), emissive);

    for (l = 0; l < 4; l++)
    {
        GELightComputation comp = (GELightComputation)(gstate.ltype[l] & 3);
        GELightType type = (GELightType)((gstate.ltype[l] >> 8) & 3);
        if (gstate.lightEnable[l])
        {
            Vec3 toLight, dir;
            s32 doSpecular, poweredDiffuse;
            float distance;
            float lightScale = 1.0f;
            float dot;
            Color4 diff, spec;

            if (type == GE_LIGHTTYPE_DIRECTIONAL)
                vec_initfromarr(&toLight, gstate.lightpos[l]);
            else
                vec_initfromarr(&toLight, gstate.lightpos[l]);
                toLight = vec_subtract(toLight, pos);

            vec_initfromarr(&dir, gstate.lightdir[l]);

            doSpecular = (comp != GE_LIGHTCOMP_ONLYDIFFUSE);
            poweredDiffuse = comp == GE_LIGHTCOMP_BOTHWITHPOWDIFFUSE;

            distance = vec_normalize(&toLight);

            if (type != GE_LIGHTTYPE_DIRECTIONAL)
            {
                lightScale = 1.0f / (gstate.lightatt[l][0] + gstate.lightatt[l][1] * distance
                    + gstate.lightatt[l][2] * distance * distance);
                if (lightScale > 1.0f)
                    lightScale = 1.0f;
            }

            dot = vec_multiply(toLight, norm);
            if (dot < 0)
                dot = 0;

            /* Do shade mapping while we're here */
            switch (tmapMode)
            {
            case 0:
                break; /* don't touch UV */

            case 2: /* shade mapping */
                if (l == uLight)
                    uvInOut[0] = 0.5f * (dot + 1.0f);
                if (l == vLight)
                    uvInOut[1] = 0.5f * (dot + 1.0f);
                break;
                /* case 1 projection mapping is handled outside since it's not light dependent */
            }

            /* end shade mapping */
            if (poweredDiffuse)
                dot = pow(dot, specCoef);

            diff = color_multiplybyflt(color_multiply(gstate.lightColor[1][l], diffuse), dot * lightScale);
            color_initFromRGBA(&spec, 0, 0, 0, 0);

            if (doSpecular)
            {
                Vec3 halfVec = toLight;
                halfVec = vec_add(halfVec, vec_normalized(&viewer));
                vec_normalize(&halfVec);

                dot = vec_multiply(halfVec, norm);
                if (dot >= 0)
                {
                    spec = color_add(spec, color_multiplybyflt(color_multiply(gstate.lightColor[2][l], specular), pow(dot, specCoef) * lightScale));
                }
            }

            lightSum = color_add(lightSum, color_add( color_multiply(gstate.lightColor[0][l], ambient), color_add(diff , spec)));
        }
    }

    if (tmapMode == 1) /* projection mapping */
    {
        Vec3 in;
        float uvw[3];
        vec_initfromxyz(&in, 0, 0, 0);
        switch (projStyle)
        {
        case 0:
            in = pos;
            break;

        case 1:
            in.x = uvInOut[0];
            in.y = uvInOut[1];
            break;

        case 2:
            in = normal;
            break;

        case 3:
            in = norm;
            break;
        }
        Vec3ByMatrix43(uvw, uvInOut, tgenMatrix);
        uvInOut[0] = uvw[0];
        uvInOut[1] = uvw[1];
    }
    colorOut[0] = lightSum.r;
    colorOut[1] = lightSum.g;
    colorOut[2] = lightSum.b;
    colorOut[3] = lightSum.a;
}

